package universe.utils;

public class Vector3D implements Cloneable {
	private final static int dimention = 3;
	public double x, y, z;

	/**
	 * Get the resulting vector after a bounce against a plane. Uses the
	 * formula: "R = 2*(-I dot N)*N + I" (N is the surface normal, I is the
	 * normalized direction of the original vector)
	 * 
	 * @param vel
	 *            the initial vector before the bounce
	 * @param normal
	 *            the normal of the surface to bounce the vector around
	 * @param result
	 *            the resulting bounced vector
	 */
	public static void getBounceVect(Vector3D vel, Vector3D normal,
			Vector3D result) {
		Vector3D vel_i;

		double len = (double) vel.mod();
		vel.normalize();

		vel_i = new Vector3D(vel);
		vel_i.invert();

		result.set(0f, 0f, 0f);
		normal.mult(2 * normal.dot(vel_i), result);

		result.add(vel);
		result.mult(len);
	}

	public Vector3D(double x, double y, double z) {
		this.x = x;
		this.y = y;
		this.z = z;
	}

	public Vector3D() {
		this(0, 0, 0);
	}

	public Vector3D(float x, float y, float z) {
		this((double) x, (double) y, (double) z);
	}

	public Vector3D(Vector3D v) {
		this(v.x, v.y, v.z);
	}

	public Vector3D(double[] coordinates) {
		this(coordinates[0], coordinates[1], coordinates[2]);
	}

	// ------------ Operations --------------------

	public void add(Vector3D v) {
		x += v.x;
		y += v.y;
		z += v.z;
	}

	public void sub(Vector3D v) {
		x -= v.x;
		y -= v.y;
		z -= v.z;
	}

	public double dot(Vector3D v) {
		return x * v.x + y * v.y + z * v.z;
	}

	public void mult(double scalar) {
		x *= scalar;
		y *= scalar;
		z *= scalar;
	}

	public void div(double scalar) {
		if (scalar == 0) {
			throw new IllegalArgumentException("Can't divide by zero!");
		}
		mult(1 / scalar);
	}

	public Vector3D cross(Vector3D v) {
		return new Vector3D(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y
				* v.x);
	}

	public void invert() {
		mult(-1);
	}

	public void normalize() {
		div((double) mod());
	}

	public double mod() {
		return Math.sqrt((x * x) + (y * y) + (z * z));
	}

	@Override
	public String toString() {
		return "[ " + x + ", " + y + ", " + z + "]";
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null || !(obj instanceof Vector3D)) {
			return false;
		}
		Vector3D v = (Vector3D) obj;
		return x == v.x && y == v.y && z == v.z;
	}

	@Override
	public int hashCode() {
		return Double.valueOf(x).hashCode() + Double.valueOf(y).hashCode()
				+ Double.valueOf(z).hashCode();
	}

	public int dimention() {
		return dimention;
	}

	@Override
	public Vector3D clone() {
		return new Vector3D(this);
	}

	public void set(double x, double y, double z) {
		this.x = x;
		this.y = y;
		this.z = z;
	}

	public void set(Vector3D v) {
		set(v.x, v.y, v.z);
	}

	public double distanceTo(Vector3D v) {
		return Math.sqrt(Math.pow(x - v.x, 2) + Math.pow(y - v.y, 2)
				+ Math.pow(z - v.z, 2));
	}

	public double[] toArray() {
		return new double[] { x, y, z };
	}

	// ----------------------------------------------------

	public void mult(double scalar, Vector3D result) {
		result.set(x * scalar, y * scalar, z * scalar);
	}

	public void div(double scalar, Vector3D result) {
		mult(1 / scalar, result);
	}

	public void add(Vector3D s, Vector3D result) {
		result.set(x + s.x, y + s.y, z + s.y);
	}

	public void sub(Vector3D s, Vector3D result) {
		result.set(x - s.x, y - s.y, z - s.y);
	}

	/**
	 * Gets the angle between 2 vectors.
	 * 
	 * @param v
	 *            The second vector
	 * @return The angle (double)
	 */
	public double getAngle(Vector3D v) {
		double f;
		f = (double) Math.acos(this.dot(v) / (this.mod() * v.mod()));
		return f * (double) 180 / (double) Math.PI;
	}
}
